/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2006
* Sleepycat Software. All rights reserved.
*
* $Id: LastFileReader.java,v 1.1 2006/05/06 09:00:03 ckaestne Exp $
*/
package com.sleepycat.je.log;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.Tracer;
/**
* LastFileReader traverses the last log file, doing checksums and looking for
* the end of the log. Different log types can be registered with it and it
* will remember the last occurrence of targetted entry types.
*/
public class LastFileReader extends FileReader {
/* Log entry types to track. */
private Set trackableEntries;
private long nextUnprovenOffset;
private long lastValidOffset;
private LogEntryType entryType;
/*
* Last lsn seen for tracked types. Key = LogEntryType, data is the offset
* (Long).
*/
private Map lastOffsetSeen;
/**
* This file reader is always positioned at the last file.
*/
public LastFileReader(EnvironmentImpl env,
int readBufferSize)
throws IOException, DatabaseException {
super(env, readBufferSize, true, DbLsn.NULL_LSN, new Long(-1),
DbLsn.NULL_LSN, DbLsn.NULL_LSN);
trackableEntries = new HashSet();
lastOffsetSeen = new HashMap();
lastValidOffset = 0;
nextUnprovenOffset = 0;
anticipateChecksumErrors = true;
}
/**
* Ctor which allows passing in the file number we want to read to the end
* of. This is used by the ScavengerFileReader when it encounters a bad
* log record in the middle of a file.
*/
public LastFileReader(EnvironmentImpl env,
int readBufferSize,
Long specificFileNumber)
throws IOException, DatabaseException {
super(env, readBufferSize, true, DbLsn.NULL_LSN,
specificFileNumber, DbLsn.NULL_LSN, DbLsn.NULL_LSN);
trackableEntries = new HashSet();
lastOffsetSeen = new HashMap();
lastValidOffset = 0;
nextUnprovenOffset = 0;
anticipateChecksumErrors = true;
}
/**
* Override so that we always start at the last file.
*/
protected void initStartingPosition(long endOfFileLsn,
Long singleFileNum)
throws IOException, DatabaseException {
eof = false;
/*
* Start at what seems like the last file. If it doesn't exist, we're
* done.
*/
Long lastNum = ((singleFileNum != null) &&
(singleFileNum.longValue() >= 0)) ?
singleFileNum :
fileManager.getLastFileNum();
FileHandle fileHandle = null;
readBufferFileEnd = 0;
long fileLen = 0;
while ((fileHandle == null) && !eof) {
if (lastNum == null) {
eof = true;
} else {
try {
readBufferFileNum = lastNum.longValue();
fileHandle = fileManager.getFileHandle(readBufferFileNum);
/*
* Check the size of this file. If it opened successfully
* but only held a header or is 0 length, backup to the
* next "last" file. Note that an incomplete header will
* end up throwing a checksum exception, but a 0 length
* file will open successfully in read only mode.
*/
fileLen = fileHandle.getFile().length();
if (fileLen <= FileManager.firstLogEntryOffset()) {
lastNum = fileManager.getFollowingFileNum
(lastNum.longValue(), false);
fileHandle.release();
fileHandle = null;
}
} catch (DatabaseException e) {
lastNum = attemptToMoveBadFile(e);
fileHandle = null;
} finally {
if (fileHandle != null) {
fileHandle.release();
}
}
}
}
nextEntryOffset = 0;
}
/**
* Something is wrong with this file. If there is no data in this file (the
* header is <= the file header size) then move this last file aside and
* search the next "last" file. If the last file does have data in it,
* throw an exception back to the application, since we're not sure what to
* do now.
*/
private Long attemptToMoveBadFile(DatabaseException origException)
throws DatabaseException, IOException {
String fileName = fileManager.getFullFileNames(readBufferFileNum)[0];
File problemFile = new File(fileName);
Long lastNum = null;
if (problemFile.length() <= FileManager.firstLogEntryOffset()) {
fileManager.clear(); // close all existing files
/* Move this file aside. */
lastNum = fileManager.getFollowingFileNum(readBufferFileNum,
false);
fileManager.renameFile(readBufferFileNum,
FileManager.BAD_SUFFIX);
} else {
/* There's data in this file, throw up to the app. */
throw origException;
}
return lastNum;
}
public void setEndOfFile()
throws IOException, DatabaseException {
fileManager.truncateLog(readBufferFileNum, nextUnprovenOffset);
}
/**
* @return The LSN to be used for the next log entry.
*/
public long getEndOfLog() {
return DbLsn.makeLsn(readBufferFileNum, nextUnprovenOffset);
}
public long getLastValidLsn() {
return DbLsn.makeLsn(readBufferFileNum, lastValidOffset);
}
public long getPrevOffset() {
return lastValidOffset;
}
public LogEntryType getEntryType() {
return entryType;
}
/**
* Tell the reader that we are interested in these kind of entries.
*/
public void setTargetType(LogEntryType type) {
trackableEntries.add(type);
}
/**
* @return The last LSN seen in the log for this kind of entry, or null.
*/
public long getLastSeen(LogEntryType type) {
Long typeNumber =(Long) lastOffsetSeen.get(type);
if (typeNumber != null) {
return DbLsn.makeLsn(readBufferFileNum, typeNumber.longValue());
} else {
return DbLsn.NULL_LSN;
}
}
/**
* Validate the checksum on each entry, see if we should remember the LSN
* of this entry.
*/
protected boolean processEntry(ByteBuffer entryBuffer) {
/* Skip over the data, we're not doing anything with it. */
entryBuffer.position(entryBuffer.position() + currentEntrySize);
/* If we're supposed to remember this lsn, record it. */
entryType = new LogEntryType(currentEntryTypeNum,
currentEntryTypeVersion);
if (trackableEntries.contains(entryType)) {
lastOffsetSeen.put(entryType, new Long(currentEntryOffset));
}
return true;
}
/**
* readNextEntry will stop at a bad entry.
* @return true if an element has been read.
*/
public boolean readNextEntry()
throws DatabaseException, IOException {
boolean foundEntry = false;
nextUnprovenOffset = nextEntryOffset;
try {
/*
* At this point,
* currentEntryOffset is the entry we just read.
* nextEntryOffset is the entry we're about to read.
* currentEntryPrevOffset is 2 entries ago.
* Note that readNextEntry() moves all the offset pointers up.
*/
foundEntry = super.readNextEntry();
/*
* Note that initStartingPosition() makes sure that the file header
* entry is valid. So by the time we get to this method, we know
* we're at a file with a valid file header entry.
*/
lastValidOffset = currentEntryOffset;
} catch (DbChecksumException e) {
Tracer.trace(Level.INFO,
env, "Found checksum exception while searching " +
" for end of log. Last valid entry is at " +
DbLsn.toString
(DbLsn.makeLsn(readBufferFileNum, lastValidOffset)) +
" Bad entry is at " +
DbLsn.makeLsn(readBufferFileNum, currentEntryOffset));
}
return foundEntry;
}
}